/*********************************  Header ********************************\
Author:       Michal Jankowski - www.mjankowski.org
Project:      MJankowski.org.AutoMock
Copyright:    (c)2015, mjankowski.org

This source is subject to the Microsoft Public License.
See http://www.microsoft.com/en-us/openness/licenses.aspx.
All other rights reserved.

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\***************************************************************************/

using MJankowski.org.AutoMock.MockFactories;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace MJankowski.org.AutoMock.Internals
{
    internal class AutoMockCore<TTargetType> where TTargetType : class
    {
        private IMockingFactory _mockingFactory;

        public Type TargetType { get; private set; }

        public DependencyContainer DependencyContainer { get; private set; }
        public DependencyContainer MocksContainer { get; private set; }

        private void initialise()
        {
            TargetType = typeof(TTargetType);
            MocksContainer = new DependencyContainer();
            DependencyContainer = new DependencyContainer();
            _mockingFactory = new NSubstituteMockingFactory();
        }

        public AutoMockCore()
        {
            initialise();
        }

        public AutoMockCore(IMockingFactory mockingFactory)
        {
            initialise();

            _mockingFactory = mockingFactory;
        }

        public AutoMockCore(DependencyContainer dependencyContainer)
        {
            initialise();

            DependencyContainer = dependencyContainer;
        }

        public AutoMockCore(IMockingFactory mockingFactory, DependencyContainer dependencyContainer)
        {
            initialise();

            _mockingFactory = mockingFactory;
            DependencyContainer = dependencyContainer;
        }


        /// <summary>
        /// Finds single constructor with parameters.
        /// Instantiates TTargetType and fills it with dependencies.
        /// Uses provided dependencies or creates mocks.
        /// </summary>
        /// <param name="selectConstructorFunc">Function for selecting constructor for building TTargetType</param>
        /// <returns>TTargetType instance.</returns>
        /// <exception cref="ArgumentNullException">for null selectConstructorFunc.</exception>
        public TTargetType BuildTargetWithSelectedConstructor(Func<ConstructorInfo[], ConstructorInfo> selectConstructorFunc)
        {
            if (selectConstructorFunc == null) throw new ArgumentNullException("selectConstructorFunc");
            MocksContainer.Clear();

            var constructorInfos = TargetType.GetConstructors();
            var constructorInfo = selectConstructorFunc(constructorInfos);

            var parametersList = new List<object>();
            foreach (var parameterInfo in constructorInfo.GetParameters())
            {
                object dependency = findDependency(parameterInfo);
                if (dependency != null)
                {
                    parametersList.Add(dependency);
                    continue;
                }

                var mock = _mockingFactory.CreateMock(parameterInfo.ParameterType);
                
                parametersList.Add(mock);
                MocksContainer.AddDependencyInstance(parameterInfo.ParameterType, mock, parameterInfo.Name);
            }

            return constructorInfo.Invoke(parametersList.ToArray()) as TTargetType;
        }

        private object findDependency(ParameterInfo parameterInfo)
        {
            if (DependencyContainer.ContainsDependencyImplementation(parameterInfo.ParameterType, parameterInfo.Name))
                return DependencyContainer.GetParentTypeDependency(parameterInfo.ParameterType, parameterInfo.Name);

            if (DependencyContainer.ContainsDependencyImplementation(parameterInfo.ParameterType))
                return DependencyContainer.GetParentTypeDependency(parameterInfo.ParameterType);

            return null;
        }
    }
}